﻿using System;
using System.Text;
using System.Collections.Generic;
using System.Linq;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using System.Threading;
using System.Diagnostics;
using System.Threading.Tasks;
using Gadgeteer.Modules.Community;

namespace Gadgeteer.Modules.Community.XBee.Test.Unit
{
    /// <summary>
    /// These tests verify that the XBeeClient handles various types of serial port scenarios. Usually, the serial port 
    /// raises a data received event and there are only a couple of message bytes available.
    /// The private method serialLine_DataReceived in XBeeClient tries to determine when an entire message has been read
    /// by using the length byte (offset 3), and checking for the checksum byte.
    /// </summary>
    [TestClass]
    public class SerialLineReaderTests
    {
        [TestMethod]
        public void DiscoverNodesWithFullMessage()
        {
            MockSerialLine mockSerialLine = new MockSerialLine();

            XBeeClient client = new XBeeClient(mockSerialLine);
            bool discoverNodesCompleted = false;
            client.DiscoverNodesCompleted += new DiscoverNodesCompletedEventHandler((o, e) =>
            {
                discoverNodesCompleted = true;
            });

            // 0x7E,0x00,0x19,0x88,0x01,0x4E,0x44,0x00,0x9E,0x88,0x00,0x7D,0x33,0xA2,0x00,0x40,0x8A,0x86,0x74,0x20,0x00,0xFF,0xFE,0x01,0x00,0xC1,0x05,0x10,0x1E,0x33
            byte[] fullMessage = new byte[] { 0x7E, 0x00, 0x19, 0x88, 0x01, 0x4E, 0x44, 0x00, 0x9E, 0x88, 0x00, 0x7D, 0x33, 0xA2, 0x00, 0x40, 0x8A, 0x86, 0x74, 0x20, 0x00, 0xFF, 0xFE, 0x01, 0x00, 0xC1, 0x05, 0x10, 0x1E, 0x33 };

            mockSerialLine.FakeBuffer = new Queue<byte>(fullMessage);
       
            mockSerialLine.RaiseBytesReceivedEvent();

            WaitUntil(() => { return discoverNodesCompleted; }, 2000, "Discover Nodes event was not fired within two seconds");

            Assert.AreEqual(0, mockSerialLine.BytesToRead);
        }

        [TestMethod]
        public void DiscoverNodesWithMessageInTwoChunks()
        {
            MockSerialLine mockSerialLine = new MockSerialLine();

            XBeeClient client = new XBeeClient(mockSerialLine);
            bool discoverNodesCompleted = false;
            client.DiscoverNodesCompleted += new DiscoverNodesCompletedEventHandler((o, e) =>
            {
                discoverNodesCompleted = true;
            });

            byte[] partialMessagePart1 = new byte[] { 0x7E, 0x00, 0x19, 0x88, 0x01, 0x4E, 0x44, 0x00, 0x9E}; 
            byte[] partialMessagePart2 = new byte[] { 0x88, 0x00, 0x7D, 0x33, 0xA2, 0x00, 0x40, 0x8A, 0x86, 0x74, 0x20, 0x00, 0xFF, 0xFE, 0x01, 0x00, 0xC1, 0x05, 0x10, 0x1E, 0x33 };

            mockSerialLine.FakeBuffer = new Queue<byte>(partialMessagePart1);

            TaskFactory taskFactory = new TaskFactory();
            taskFactory.StartNew(()=>
            {
                mockSerialLine.RaiseBytesReceivedEvent();
            });
            Thread.Sleep(500);

            Assert.AreEqual(0, mockSerialLine.BytesToRead);

            mockSerialLine.FakeBuffer = new Queue<byte>(partialMessagePart2);

            WaitUntil(() => { return discoverNodesCompleted; }, 2000, "Discover Nodes event was not fired within two seconds");

            Assert.AreEqual(0, mockSerialLine.BytesToRead);
        }

        [TestMethod]
        public void DiscoverNodesWithMessageInTwoChunksWhereFirstChunkIsBeforeLengthByte()
        {
            MockSerialLine mockSerialLine = new MockSerialLine();

            XBeeClient client = new XBeeClient(mockSerialLine);
            bool discoverNodesCompleted = false;
            client.DiscoverNodesCompleted += new DiscoverNodesCompletedEventHandler((o, e) =>
            {
                discoverNodesCompleted = true;
            });

            byte[] partialMessagePart1 = new byte[] { 0x7E, 0x00 };
            byte[] partialMessagePart2 = new byte[] { 0x19, 0x88, 0x01, 0x4E, 0x44, 0x00, 0x9E, 0x88, 0x00, 0x7D, 0x33, 0xA2, 0x00, 0x40, 0x8A, 0x86, 0x74, 0x20, 0x00, 0xFF, 0xFE, 0x01, 0x00, 0xC1, 0x05, 0x10, 0x1E, 0x33 };

            mockSerialLine.FakeBuffer = new Queue<byte>(partialMessagePart1);

            TaskFactory taskFactory = new TaskFactory();
            taskFactory.StartNew(() =>
            {
                mockSerialLine.RaiseBytesReceivedEvent();
            });
            Thread.Sleep(500);

            Assert.AreEqual(0, mockSerialLine.BytesToRead);

            mockSerialLine.FakeBuffer = new Queue<byte>(partialMessagePart2);

            WaitUntil(() => { return discoverNodesCompleted; }, 2000, "Discover Nodes event was not fired within two seconds");

            Assert.AreEqual(0, mockSerialLine.BytesToRead);
        }

        [TestMethod]
        public void TransmitStatusReceivedWithFullMessage()
        {
            MockSerialLine mockSerialLine = new MockSerialLine();

            XBeeClient client = new XBeeClient(mockSerialLine);
            bool transmitStatusReceived = false;
            client.TransmitStatusReceived += new TransmitStatusReceivedEventHandler((o, e) =>
            {
                transmitStatusReceived = true;
            });

            byte[] fullMessage = APIMessageBuilder.BuildTransmitStatus(0x00);

            mockSerialLine.FakeBuffer = new Queue<byte>(fullMessage);
       
            mockSerialLine.RaiseBytesReceivedEvent();

            WaitUntil(() => { return transmitStatusReceived; }, 2000, "Transmit Status Received event was not fired within two seconds");

            Assert.AreEqual(0, mockSerialLine.BytesToRead);
        }

        [TestMethod]
        public void TransmitReceivedWithFullMessage()
        {
            MockSerialLine mockSerialLine = new MockSerialLine();

            XBeeClient client = new XBeeClient(mockSerialLine);
            bool transmitReceived = false;
            client.TransmitReceive += new TransmitReceiveEventHandler((o, e) =>
            {
                transmitReceived = true;
            });

            // 0x7E, 0x00, 0x22, 0x90, 0x00, 0x7D, 0x33, 0xA2, 0x00, 0x40, 0x8A, 0x86, 0x74, 0x73, 0xD2, 0x02, 0x68, 0x65, 0x6C, 0x6C, 0x6F, 0x77, 0x20, 0x77, 0x6F, 0x72, 0x6C, 0x64, 0x2C, 0x20, 0x63, 0x6C, 0x69, 0x65, 0x6E, 0x74, 0x20, 0x32, 0xBF
            byte[] fullMessage = new byte[] {0x7E, 0x00, 0x22, 0x90, 0x00, 0x7D, 0x33, 0xA2, 0x00, 0x40, 0x8A, 0x86, 0x74, 0x73, 0xD2, 0x02, 0x68, 0x65, 0x6C, 0x6C, 0x6F, 0x77, 0x20, 0x77, 0x6F, 0x72, 0x6C, 0x64, 0x2C, 0x20, 0x63, 0x6C, 0x69, 0x65, 0x6E, 0x74, 0x20, 0x32, 0xBF };

            mockSerialLine.FakeBuffer = new Queue<byte>(fullMessage);

            mockSerialLine.RaiseBytesReceivedEvent();

            WaitUntil(() => { return transmitReceived; }, 2000, "Transmit Received event was not fired within two seconds");

            Assert.AreEqual(0, mockSerialLine.BytesToRead);
        }

        [TestMethod]
        public void TransmitReceivedDataCorrect()
        {
            MockSerialLine mockSerialLine = new MockSerialLine();

            XBeeClient client = new XBeeClient(mockSerialLine);
            bool transmitReceived = false;
            client.TransmitReceive += new TransmitReceiveEventHandler((o, e) =>
            {
                // hello
                byte[] expected = new byte[] { 0x68, 0x65, 0x6c, 0x6c, 0x6f };
                AssertArraysEqual(expected, e.Result.ReceivedData);
                transmitReceived = true;
            });


            byte[] fullMessage = new byte[] { 0x7E, 0x00, 0x11, 0x90, 0x00, 0x13, 0xA2, 0x00, 0x40, 0x52, 0x2B, 0xAA, 0x7C, 0x84, 0x01, 0x68, 0x65, 0x6c, 0x6c, 0x6f, 0x3C };
        
            mockSerialLine.FakeBuffer = new Queue<byte>(fullMessage);

            mockSerialLine.RaiseBytesReceivedEvent();

            WaitUntil(() => { return transmitReceived; }, 2000, "Transmit Received event was not fired within two seconds");

            Assert.AreEqual(0, mockSerialLine.BytesToRead);
        }

        private static ushort CalculateChecksum(byte[] message)
        {
            int sum = 0;
            int startIndex = 3; // checksum is calculated from the FrameType up to the checksum
            for (int i = startIndex; i < message.Length - 1; i++)
            {
                sum += (ushort)message[i];
            }
            int lower8Bits = 0xFF & sum;

            int checksum = 0xFF - lower8Bits;
            return (ushort)checksum;
        }

        private void AssertArraysEqual(byte[] expected, byte[] actual)
        {
            Assert.AreEqual(expected.Length, actual.Length);
            for (int i = 0; i < expected.Length; i++)
            {
                Assert.AreEqual(expected[i], actual[i]);
            }
        }

        private void WaitUntil(Func<bool> eval, int timeout, string failMessage)
        {
            Stopwatch stopwatch = Stopwatch.StartNew();
            while (!eval())
            {
                if (stopwatch.ElapsedMilliseconds > timeout)
                {
                    Assert.Fail(failMessage);
                }

                Thread.Sleep(100);
            }

        }
    }
}
